/**
 * Copyright (C) 2011-2015 The XDocReport Team <xdocreport@googlegroups.com>
 *
 * All rights reserved.
 *
 * Permission is hereby granted, free  of charge, to any person obtaining
 * a  copy  of this  software  and  associated  documentation files  (the
 * "Software"), to  deal in  the Software without  restriction, including
 * without limitation  the rights to  use, copy, modify,  merge, publish,
 * distribute,  sublicense, and/or sell  copies of  the Software,  and to
 * permit persons to whom the Software  is furnished to do so, subject to
 * the following conditions:
 *
 * The  above  copyright  notice  and  this permission  notice  shall  be
 * included in all copies or substantial portions of the Software.
 *
 * THE  SOFTWARE IS  PROVIDED  "AS  IS", WITHOUT  WARRANTY  OF ANY  KIND,
 * EXPRESS OR  IMPLIED, INCLUDING  BUT NOT LIMITED  TO THE  WARRANTIES OF
 * MERCHANTABILITY,    FITNESS    FOR    A   PARTICULAR    PURPOSE    AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE,  ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package fr.opensagres.xdocreport.core.io.internal;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.logging.Logger;

import fr.opensagres.xdocreport.core.io.IOUtils;
import fr.opensagres.xdocreport.core.logging.LogUtils;

/**
 * An Iterator over the lines in a <code>Reader</code>.
 * <p>
 * <code>LineIterator</code> holds a reference to an open <code>Reader</code>. When you have finished with the iterator
 * you should close the reader to free internal resources. This can be done by closing the reader directly, or by
 * calling the {@link #close()} or {@link #closeQuietly(LineIterator)} method on the iterator.
 * <p>
 * The recommended usage pattern is:
 * 
 * <pre>
 * LineIterator it = FileUtils.lineIterator( file, &quot;UTF-8&quot; );
 * try
 * {
 *     while ( it.hasNext() )
 *     {
 *         String line = it.nextLine();
 *         // / do something with line
 *     }
 * }
 * finally
 * {
 *     LineIterator.closeQuietly( iterator );
 * }
 * </pre>
 * 
 * @author Niall Pemberton
 * @author Stephen Colebourne
 * @author Sandy McArthur
 * @version $Id: LineIterator.java 437567 2006-08-28 06:39:07Z bayard $
 * @since Commons IO 1.2
 */
public class LineIterator
    implements Iterator<String>
{
    /**
     * Logger for this class
     */
    private static final Logger logger = LogUtils.getLogger( LineIterator.class.getName() );

    /** The reader that is being read. */
    private final BufferedReader bufferedReader;

    /** The current line. */
    private String cachedLine;

    /** A flag indicating if the iterator has been fully read. */
    private boolean finished = false;

    /**
     * Constructs an iterator of the lines for a <code>Reader</code>.
     * 
     * @param reader the <code>Reader</code> to read from, not null
     * @throws IllegalArgumentException if the reader is null
     */
    public LineIterator( final Reader reader )
        throws IllegalArgumentException
    {
        if ( reader == null )
        {
            throw new IllegalArgumentException( "Reader must not be null" );
        }
        if ( reader instanceof BufferedReader )
        {
            bufferedReader = (BufferedReader) reader;
        }
        else
        {
            bufferedReader = new BufferedReader( reader );
        }
    }

    // -----------------------------------------------------------------------
    /**
     * Indicates whether the <code>Reader</code> has more lines. If there is an <code>IOException</code> then
     * {@link #close()} will be called on this instance.
     * 
     * @return <code>true</code> if the Reader has more lines
     * @throws IllegalStateException if an IO exception occurs
     */
    public boolean hasNext()
    {
        if ( cachedLine != null )
        {
            return true;
        }
        else if ( finished )
        {
            return false;
        }
        else
        {
            try
            {
                while ( true )
                {
                    String line = bufferedReader.readLine();
                    if ( line == null )
                    {
                        finished = true;
                        return false;
                    }
                    else if ( isValidLine( line ) )
                    {
                        cachedLine = line;
                        return true;
                    }
                }
            }
            catch ( IOException ioe )
            {
                logger.severe( "exception: " + ioe ); //$NON-NLS-1$

                close();
                throw new IllegalStateException( ioe.toString() );
            }
        }
    }

    /**
     * Overridable method to validate each line that is returned.
     * 
     * @param line the line that is to be validated
     * @return true if valid, false to remove from the iterator
     */
    protected boolean isValidLine( String line )
    {
        return true;
    }

    /**
     * Returns the next line in the wrapped <code>Reader</code>.
     * 
     * @return the next line from the input
     * @throws NoSuchElementException if there is no line to return
     */
    public String next()
    {
        return nextLine();
    }

    /**
     * Returns the next line in the wrapped <code>Reader</code>.
     * 
     * @return the next line from the input
     * @throws NoSuchElementException if there is no line to return
     */
    public String nextLine()
    {
        if ( !hasNext() )
        {
            throw new NoSuchElementException( "No more lines" );
        }
        String currentLine = cachedLine;
        cachedLine = null;
        return currentLine;
    }

    /**
     * Closes the underlying <code>Reader</code> quietly. This method is useful if you only want to process the first
     * few lines of a larger file. If you do not close the iterator then the <code>Reader</code> remains open. This
     * method can safely be called multiple times.
     */
    public void close()
    {
        finished = true;
        IOUtils.closeQuietly( bufferedReader );
        cachedLine = null;
    }

    /**
     * Unsupported.
     * 
     * @throws UnsupportedOperationException always
     */
    public void remove()
    {
        throw new UnsupportedOperationException( "Remove unsupported on LineIterator" );
    }

    // -----------------------------------------------------------------------
    /**
     * Closes the iterator, handling null and ignoring exceptions.
     * 
     * @param iterator the iterator to close
     */
    public static void closeQuietly( LineIterator iterator )
    {
        if ( iterator != null )
        {
            iterator.close();
        }
    }

}
